#!/usr/bin/env python
""" Generic utility functions for ubiqis """

import anydbm
import logging
import os
import random
import re
import socket
import tempfile
import time
import urllib2
import zipfile
import xml.etree.ElementTree as ET

from base64 import b64decode
from hashlib import sha1
from Crypto.Util import number

from ubiqis import keytool
from ubiqis import names

def get_external_ip(verbose=False):
    """
    Get the external ip address as seen from http://whatismyip.org/ or similar
    site and cache the result in the home directory. Update it at most once per hour.
    """

    ip_hosts = [
        'http://ipid.shat.net/iponly/',
        'http://www.vidalia-project.net/iptest/ip.php',
        'http://www.alexnolan.net/ip/',
        'http://whatismyip.org',
        'http://www.freehaven.net/~edmanm/ip.php',
        'http://myip.dk'
    ]

    ip_cache = None
    if os.environ.has_key("HOME"):
        ip_cache = os.path.join(os.environ['HOME'],".ip_cache")
    else:
        ip_cache = os.path.join(tempfile.gettempdir(),".ip_cache."+str(os.getuid()))
    ip = None
    store = True

    if os.path.isfile(ip_cache):
        store = False
        st = os.stat(ip_cache)
        tdiff = time.time()-st[8]
        if tdiff > 3600.0 or st[4] != os.getuid():
            store = True
        fp = open(ip_cache,"r")
        text = fp.read()
        g=re.search(r'\b\d+\.\d+\.\d+\.\d+\b',text)
        if g:
            ip = g.group(0)
        else:
            store = True
        fp.close()

    if store:
        # Don't always use the same ip host, distribute the load
        random.shuffle(ip_hosts)
        for ip_host in ip_hosts:
            text = ""
            if verbose:
                print "trying",ip_host
            try:
                text = urllib2.urlopen(ip_host).read()
            except:
                continue
            g=re.search(r'\b\d+\.\d+\.\d+\.\d+\b',text)
            if g:
                ip = g.group(0)
                try:
                    fp = open(ip_cache,"w")
                    fp.writelines([ip])
                    fp.close()
                except:
                    if verbose:
                        print "Could not write "+ip_cache
                break
            elif verbose:
                print 'parse failed: ',text
    return ip

def get_internal_ip():
    """ return system ip """
    return socket.gethostbyname(socket.gethostname())

def verify_sig(file):
    """ returns true if signed ubiq file completes verification """

    ubiq = open(file).read()
    db_file = os.path.join(os.environ['UBIQIS_CACHE_DIR'], 'trusted.db')
    trusted_keys = anydbm.open(db_file, 'c')

    # TODO:  Have this option be configurable via config file
    num_success = 1

    def keycheck(host, key, name):
        def foundkey(loc):
            logging.debug('Verify: Found matching key from: %s\n' % loc)
            return True

        if trusted_keys.has_key(host) and trusted_keys[host] == key:
            return foundkey('Local Cache: %s' % db_file)
        else:
            url = 'http://%s/getkey' % host
            try:
                temp = urllib2.urlopen(url).read()
            except urllib2.URLError:
                url = 'http://%s/signpkg?url=%s' % (host, names.pkg2url(name))
                try:
                    temp = urllib2.urlopen(url).read()
                except urllib2.URLError, msg:
                    logging.debug("Verify: Error, can't verify key.\n%s\n" % msg)
                else:
                    tree = ET.fromstring(temp)
                    sigs = tree.findall('signature')
                    for sig in sigs:
                        if sig.findtext('public-key') == key:
                            trusted_keys[host] = key
                            return foundkey(url)
                    return False
            else:
                if temp == key:
                    trusted_keys[host] = key
                    return foundkey(url)

    # Create a tree from the XML input.
    tree = ET.fromstring(ubiq)
    name = tree.findtext('name')
    signatures = tree.findall('signature')
    # Separate the signature(s) from the original .ubiq
    for sig in signatures:
        tree.remove(sig)
    # Recreate hash used in signing
    orig_hash = sha1()
    orig_hash.update(''.join(ET.tostring(tree).split()))
    verified = []
    for signature in signatures:
        host = signature.findtext('host')
        pub_key = signature.findtext('public-key')
        if keycheck(host, pub_key, name):
            pub_key = keytool.key_from_string(b64decode(pub_key))
            # Recreate signature data in tuplized, long form
            sig_data = b64decode(signature.findtext('signed-data'))
            sig_data = (number.bytes_to_long(sig_data),)
            # Generate the public key object
            if pub_key.verify(orig_hash.hexdigest(), sig_data):
                verified.append('host %s: Success' % host)
                logging.debug('Verify: Host %s: Success' % host)
            else:
                logging.debug('Verify: Host %s: Failed' % host)

    if len(verified) >= num_success:
        return True
    else:
        return False

def unzip(file, dir=None):
    """ unzip a zipfile into a directory """
    if dir is None:
        dir = os.curdir
    else:
        if not os.path.exists(dir):
            os.makedirs(dir)
    logging.debug('unzip dir: '+os.path.abspath(dir))
    zf = zipfile.ZipFile(file)
    for name in zf.namelist():
        if name.endswith('/'):
            if not os.path.exists(os.path.join(dir, name)):
                logging.debug('making dir '+name)
                os.makedirs(os.path.join(dir, name))
        else:
            if not os.path.exists(os.path.dirname(os.path.join(dir, name))):
                os.makedirs(os.path.dirname(os.path.join(dir, name)))
            logging.debug('writing out '+os.path.join(dir, name))
            outfile = open(os.path.join(dir, name), 'wb')
            outfile.write(zf.read(name))
            outfile.close()
    
